/**
 * Copyright (C) 2023 maminjie <canpool@163.com>
 * Copyright (C) 2022 Uwe Kindler
 * SPDX-License-Identifier: LGPL-2.1
 **/

#include "QxDockResizeHandle.h"

#include <QDebug>
#include <QMouseEvent>
#include <QPointer>
#include <QRubberBand>
#include <QStyle>
#include <QStyleOption>

QX_BEGIN_NAMESPACE

/**
 * Private data class of DockResizeHandle class (pimpl)
 */
struct DockResizeHandlePrivate {
    DockResizeHandle *_this;
    Qt::Edge HandlePosition = Qt::LeftEdge;
    QWidget *Target = nullptr;
    int MouseOffset = 0;
    bool Pressed = false;
    int MinSize = 0;
    int MaxSize = 1;
    QPointer<QRubberBand> RubberBand;
    bool OpaqueResize = false;
    int HandleWidth = 4;

    /**
     * Private data constructor
     */
    DockResizeHandlePrivate(DockResizeHandle *_public);

    /**
     * Pick position component from pos depending on orientation
     */
    int pick(const QPoint &pos) const
    {
        return _this->orientation() == Qt::Horizontal ? pos.x() : pos.y();
    }

    /**
     * Returns true, if orientation is horizontal
     */
    bool isHorizontal() const
    {
        return _this->orientation() == Qt::Horizontal;
    }

    /**
     * Set rubberband position
     */
    void setRubberBand(int Pos);

    /**
     * Calculates the resize position and geometry
     */
    void doResizing(QMouseEvent *e, bool ForceResize = false);
};
// struct DockResizeHandlePrivate

DockResizeHandlePrivate::DockResizeHandlePrivate(DockResizeHandle *_public) : _this(_public)
{
}

void DockResizeHandlePrivate::setRubberBand(int Pos)
{
    if (!RubberBand) {
        RubberBand = new QRubberBand(QRubberBand::Line, Target->parentWidget());
    }

    auto Geometry = _this->geometry();
    auto TopLeft = Target->mapTo(Target->parentWidget(), Geometry.topLeft());
    switch (HandlePosition) {
    case Qt::LeftEdge:
    case Qt::RightEdge:
        TopLeft.rx() += Pos;
        break;
    case Qt::TopEdge:
    case Qt::BottomEdge:
        TopLeft.ry() += Pos;
        break;
    }

    Geometry.moveTopLeft(TopLeft);
    RubberBand->setGeometry(Geometry);
    RubberBand->show();
}

void DockResizeHandlePrivate::doResizing(QMouseEvent *e, bool ForceResize)
{
    int pos = pick(e->pos()) - MouseOffset;
    auto OldGeometry = Target->geometry();
    auto NewGeometry = OldGeometry;
    switch (HandlePosition) {
    case Qt::LeftEdge: {
        NewGeometry.adjust(pos, 0, 0, 0);
        int Size = qBound(MinSize, NewGeometry.width(), MaxSize);
        pos += (NewGeometry.width() - Size);
        NewGeometry.setWidth(Size);
        NewGeometry.moveTopRight(OldGeometry.topRight());
    } break;

    case Qt::RightEdge: {
        NewGeometry.adjust(0, 0, pos, 0);
        int Size = qBound(MinSize, NewGeometry.width(), MaxSize);
        pos -= (NewGeometry.width() - Size);
        NewGeometry.setWidth(Size);
    } break;

    case Qt::TopEdge: {
        NewGeometry.adjust(0, pos, 0, 0);
        int Size = qBound(MinSize, NewGeometry.height(), MaxSize);
        pos += (NewGeometry.height() - Size);
        NewGeometry.setHeight(Size);
        NewGeometry.moveBottomLeft(OldGeometry.bottomLeft());
    } break;

    case Qt::BottomEdge: {
        NewGeometry.adjust(0, 0, 0, pos);
        int Size = qBound(MinSize, NewGeometry.height(), MaxSize);
        pos -= (NewGeometry.height() - Size);
        NewGeometry.setHeight(Size);
    } break;
    }

    if (_this->opaqueResize() || ForceResize) {
        Target->setGeometry(NewGeometry);
    } else {
        setRubberBand(pos);
    }
}

DockResizeHandle::DockResizeHandle(Qt::Edge HandlePosition, QWidget *parent) : Super(parent), d(new DockResizeHandlePrivate(this))
{
    d->Target = parent;
    setMinResizeSize(48);
    setHandlePosition(HandlePosition);
}

DockResizeHandle::~DockResizeHandle()
{
    delete d;
}

void DockResizeHandle::mouseMoveEvent(QMouseEvent *e)
{
    if (!(e->buttons() & Qt::LeftButton)) {
        return;
    }

    d->doResizing(e);
}

void DockResizeHandle::mousePressEvent(QMouseEvent *e)
{
    if (e->button() == Qt::LeftButton) {
        d->MouseOffset = d->pick(e->pos());
        d->Pressed = true;
        update();
    }
}

void DockResizeHandle::mouseReleaseEvent(QMouseEvent *e)
{
    if (!opaqueResize() && e->button() == Qt::LeftButton) {
        if (d->RubberBand) {
            d->RubberBand->deleteLater();
        }
        d->doResizing(e, true);
    }
    if (e->button() == Qt::LeftButton) {
        d->Pressed = false;
        update();
    }
}

void DockResizeHandle::setHandlePosition(Qt::Edge HandlePosition)
{
    d->HandlePosition = HandlePosition;
    switch (d->HandlePosition) {
    case Qt::LeftEdge:   // fall through
    case Qt::RightEdge:
        setCursor(Qt::SizeHorCursor);
        break;

    case Qt::TopEdge:   // fall through
    case Qt::BottomEdge:
        setCursor(Qt::SizeVerCursor);
        break;
    }

    setMaxResizeSize(d->isHorizontal() ? parentWidget()->height() : parentWidget()->width());
    if (!d->isHorizontal()) {
        setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
    } else {
        setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding);
    }
}

Qt::Edge DockResizeHandle::handlePostion() const
{
    return d->HandlePosition;
}

Qt::Orientation DockResizeHandle::orientation() const
{
    switch (d->HandlePosition) {
    case Qt::LeftEdge:   // fall through
    case Qt::RightEdge:
        return Qt::Horizontal;

    case Qt::TopEdge:   // fall through
    case Qt::BottomEdge:
        return Qt::Vertical;
    }

    return Qt::Horizontal;
}

QSize DockResizeHandle::sizeHint() const
{
    QSize Result;
    switch (d->HandlePosition) {
    case Qt::LeftEdge:   // fall through
    case Qt::RightEdge:
        Result = QSize(d->HandleWidth, d->Target->height());
        break;

    case Qt::TopEdge:   // fall through
    case Qt::BottomEdge:
        Result = QSize(d->Target->width(), d->HandleWidth);
        break;
    }

    return Result;
}

bool DockResizeHandle::isResizing() const
{
    return d->Pressed;
}

void DockResizeHandle::setMinResizeSize(int MinSize)
{
    d->MinSize = MinSize;
}

void DockResizeHandle::setMaxResizeSize(int MaxSize)
{
    d->MaxSize = MaxSize;
}

void DockResizeHandle::setOpaqueResize(bool opaque)
{
    d->OpaqueResize = opaque;
}

bool DockResizeHandle::opaqueResize() const
{
    return d->OpaqueResize;
}

QX_END_NAMESPACE
